/*
Copyright (C) 2011 Salil Bhagurkar

This file is part of illusion

illusion is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

illusion is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

/*Kernel module registration*/

#include <kernel/module.h>
#include <kernel/list.h>
#include <klib/lib.h>
#include <klib/memory.h>
#include <klib/string.h>
#include <arch/multiboot.h>
#include <kernel/page.h>
#include <kernel/vmpage.h>
#include <kernel/init.h>
#include <kernel/console.h>
#include <arch/power.h>
#include <kernel/vfs.h>
#include <kernel/errors.h>
#include <kernel/kmalloc.h>

//Global variables for the kernel
struct kernel_module apimod_module;
struct vfs_node *apimod_module_fs;


static struct kernel_module *get_module_desc(char *mod_data, uint_t length)
{
	uint_t i;
	for(i = 0; i < length; i++) {
		struct kernel_module *kernel_module = (struct kernel_module *)(&mod_data[i]);
		if(streq(kernel_module->sign, KERNEL_MODULE_SIGN)) {
			return kernel_module;
		}
	}
	return null;
}

//
// TODO Add a format_string format specifier for errors.
//

int load_apimod(char *path, u32 access_flags)
{
	struct vfs_node *mod_fs;
	int err = vfs_get(&mod_fs, null, path);
	if(err != 0) {
		initmsg("Module %s not found\n");
		return err;
	}

	uint_t mod_length = mod_fs->length;
	void *mod_data = kmalloc(mod_length, "moddat");
	vfs_read(mod_fs, "data", mod_data, 0, mod_length);

	struct kernel_module *module = get_module_desc(mod_data, mod_length);
	if(module == null) {
		initmsg("No module descriptor found for module %s\n", path);
		return ESTINV;
	}
	initmsg("Loading module %s\n", module->name);

	//Compute the offset at which the header will lie from the start of
	//the module after being loaded
	uint_t header_offset = module->header - module->load;

	//Get the real start of the module by subtracting this offset
	//from the actual header start
	void *real_module_addr = (void *)module - header_offset;
	void *real_module_end = mod_data + mod_length;
	uint_t real_sz = real_module_end - real_module_addr;
	uint_t real_nr_pages = real_sz / PAGE_SIZE + !!(real_sz % PAGE_SIZE);

	initmsg("Loading: 0x%x (%u bytes)\n", module->load, real_sz);

	debug("module->load = 0x%x\n", module->load);
	debug("module->data = 0x%x\n", module->data);
	debug("module->bss = 0x%x\n", module->bss);
	debug("module->end = 0x%x\n", module->end);

	//Create an entry for the module at /modules
	struct vfs_node *lmods;
	vfs_get(&lmods, null, "illusion:/modules");

	struct vfs_node *lmod = vfs_create(lmods, module->name);

	//Write the data section to this node as it will be required by every user
	//mode application
	vfs_write(lmod, "data", real_module_addr, 0, real_sz);


	//Keep a copy of the module descriptor for the kernel
	memcpy(&apimod_module, module, sizeof(struct kernel_module));

	kfree(mod_data, mod_length, "moddat");

	apimod_module_fs = lmod;

	return 0;
}
